#include "stdafx.h"

#include "ioutils.h"
#include "MIDIproc.h"
#include "common.h"
#include "synmidi.h"
using namespace Synth;
#include <vector>
#include <algorithm>

/* Controllers to save
1 Modulation
71 filter cutoff
74 filter rez
*/

int midiContToCustomCont[128];

struct MIDIHdr {
	unsigned short type_swapped;
	unsigned short trackcount_swapped;
	unsigned short ppqn_swapped;
};

struct Event {
	int time;
	int value1;
	int value2; // ONLY stored for value0

	bool operator < (const Event& other) {
		return time < other.time;
	}
};

std::vector<Event> events[16][ME_MAX + CC_MAX];

inline unsigned short swaps(unsigned short val) {
	//return (val<<8) | (val>>8);
	return _byteswap_ushort(val);
}

void InitMIDI() {
	// TODO: memset
	for (int i = 0; i < 128; i++)
		midiContToCustomCont[i] = -1;
	midiContToCustomCont[1] = CC_MODULATION;
	midiContToCustomCont[10] = CC_PAN;
	midiContToCustomCont[71] = CC_FILTERCUTOFF;
	midiContToCustomCont[74] = CC_FILTERREZ;
}

void WriteVarLen(unsigned char *&dest, unsigned int value) {
	unsigned long buffer;
	buffer = value & 0x7F;
	while ((value >>= 7)) {
		buffer <<= 8;
		buffer |= ((value & 0x7F) | 0x80);
	}
	while (true) {
		*dest++ = buffer & 0xFF;
		if (buffer & 0x80)
			buffer >>= 8;
		else
			break;
	}
}

unsigned int ReadVarLen(unsigned char *&src) {
    unsigned int value;
    if ((value = *src++) & 0x80) {
       value &= 0x7F;
	   unsigned char c;
       do {
         value = (value << 7) + ((c = *src++) & 0x7F);
       } while (c & 0x80);
    }
    return (value);
}

int ProcessMIDIFile(const char *filename, unsigned char *output) {
	InitMIDI();
	int sz = 0;
	unsigned char *startOutput = output;
	//===========================================================
	// PHASE 1: Parse the MIDI file into easy structures
	//===========================================================
	MIDIHdr *hdr;
	IOUtils::RIFFFile midi;
	if (!midi.Open(filename)) {
		MessageBox(0,"File not found", filename, 0);	
	}
	midi.FakeOuterBlock();
	int orig_size;
	if (midi.DescendBS('MThd')) {
		hdr = (MIDIHdr*)midi.GetChunkPtr();
		midi.Ascend();
	} else {
		return -1;
	}

	int type = swaps(hdr->type_swapped);
	if (type != 1) {
		//GODDAMNIT MOFO!
		printf("ERROR: Currently only handling MIDI files of type 1.\n");
		return -1;
	}
	int trackCount = swaps(hdr->trackcount_swapped);
	int ppqn = swaps(hdr->ppqn_swapped);
	for (int t = 0; t < trackCount; t++) {
		if (!midi.DescendBS('MTrk')) {
			return -1;
		}
		unsigned char *data = (unsigned char*)midi.GetChunkPtr();
		unsigned char *startOfData = data;
		//Okay, parse midi track here
		unsigned char *endOfBlock = data + midi.GetChunkSize();

		unsigned int accTime = 0; //accumulated time

		while (data < endOfBlock) {
			//expecting delta time here
			int deltaTime = ReadVarLen(data);
			if (deltaTime < 0)
				DebugBreak();

			accTime += deltaTime;

			Event ev;
			ev.time = accTime;			

			//Okay, got ourselves the delta time, now for the message
			switch (*data++) {
			case 0xF0: //SYSEX crap
			case 0xF7:
				{
					int length = ReadVarLen(data);
					data += length;
				}
				break;
			case 0xFF: //Meta events
				switch (*data++) {
				case 0: //Sequence number
					DebugBreak();
					data++;
					break;
				case 1: //Any text
				case 2: //Copyright
				case 3: //Track Name
				case 4: //Instrument name
				case 5: //Lyric segment
				case 6: //Marker (use for synchro?)
				case 7: //Cue point 
				case 8: //Program (patch) name
				case 9: //Device (port) name
					{
						int nLen = *data++;
						data += nLen; //skip text
					}
					break;
				case 0x20: //Channel Prefix (?)
				case 0x21: //Port Prefix (?)
					data++;
					data++;
					break;
				case 0x2F: // END OF TRACK
					data++; //skip zero
					break;
				case 0x51:
					if (*data++ == 0x03) {
						//TEMPO CHANGE! should only be on track 1
						int tempo = data[2] | (data[1] << 8) | (data[0] << 16);
						data += 3;
					} else {
						return -1;
						//ERROR
					}
					break;
				case 0x58:
					if (*data++ == 0x04) {
						int numerator = *data++;
						int denominator = *data++;
						int midiclockinmetronometicks = *data++;
						int num32ndsinmidiquarter = *data++;
					} else {
						return -1;
						//ERROR
					}
					break;
				case 0x59:
					if (*data++ == 0x02) {
						int sf = *data++;
						int minor = *data++; //0 = major, 1 = minor
					} else {
						return -1;
						//ERROR
					}
					break;
				}
				break;
			
			default:
				{
					//Ooh, standard midi event! do something smart!
					static int lastCmd = -1;

					int d = data[-1];
					if (d < 0x80) {
						//ah, a repeat! evil evil
						d = lastCmd;
						data--;
					} else
						lastCmd = d;

					int channel = d & 0xF;

					switch (d & 0xF0) {
					case 0x80: //key off
						{
							int key = *data++;
							int velocity = *data++; // why?
							ev.value1 = key;
							ev.value2 = 0;
							events[channel][ME_KEYUP].push_back(ev);
							break;
						}

					case 0x90: //key on
						{
							int key = *data++;
							int velocity = *data++;
							ev.value1 = key;
							ev.value2 = velocity;
							events[channel][ME_KEYDOWN].push_back(ev);
							break;
						}

					case 0xA0: //note pressure, ignore
						{
							int note = *data++;
							int value = *data++;
							break;
						}

					case 0xB0: //controller change
						{
							int controller = *data++;
							int value = *data++;
							ev.value1 = value;
							if (midiContToCustomCont[controller] != -1)
								events[channel][ME_MAX + midiContToCustomCont[controller]].push_back(ev);
							break;
						}

					case 0xC0: //Program change
						{
							int program = *data++;
							break;
						}

					case 0xD0: //channel pressure, wtf?
						{
							data++; // skip data byte
							break;
						}

					case 0xE0: //pitch bend
						{
							int msb = *data++;
							int lsb = *data++;
							ev.value1 = msb;
							events[channel][ME_MAX + CC_PITCHBEND].push_back(ev);
							break;
						}

					default:
						DebugBreak();
					}
					break;
				}
			}
		}
		midi.Ascend();
	}
	midi.Close();
	printf("MIDI file read successfully. Processing...");

	//===========================================================
	// PHASE 2: Sort all the event lists (tracks can poke into each other's channels)
	//===========================================================
	for (int t = 0; t < 16; t++) {
		for (int chan = 0; chan < 16; chan++) {
			std::sort(events[chan][t].begin(), events[chan][t].end());
		}
	}

	//===========================================================
	// PHASE 3: Do post-processing like merging key-down/key-up, quantize stuff
	// Currently nothing is done here. We should quantize velocity information at the very least.
	//===========================================================
	for (int t = 0; t < 16; t++) {
		for (int chan = 0; chan < 16; chan++) {
			std::vector<Event> &evs = events[chan][t];
			int size = (int)evs.size();
			for (int k = 0; k < size; k++) {
				evs[k].time;
			}
		}
	}

	//===========================================================
	// PHASE 4: Mash the results into easily compressible format
	//===========================================================

	for (int t = 0; t < NUM_EVENTTYPES; t++) {
		for (int chan = 0; chan < 16; chan++) {
			int curTime = 0;
			int curVal1 = 0;
			int curVal2 = 0;
			std::vector<Event> &evs = events[chan][t];

			int count = (int)evs.size();

			//deltify the times and values
			for (int k = 0; k < count; k++) {
				Event &e = evs[k];
				if (e.time < curTime)
					DebugBreak();

				int temp = e.time;
				e.time -= curTime;
				curTime = temp;
				
				temp = e.value1;
				e.value1 -= curVal1;
				curVal1 = temp;

				temp = e.value2;
				e.value2 -= curVal2;
				curVal2 = temp;
			}

			//Okay, let's separate and store
			//First, write count
			
			WriteVarLen(output, count);
			//Then blast all time deltas
			for (int k = 0; k < count; k++) {
				int q = evs[k].time;
				if (q < 0)
					DebugBreak();

				WriteVarLen(output, q);
			}

			//Immediately blast all val1
			for (int k = 0; k < count; k++)
				*output++ = evs[k].value1;
			//Then, of course, all the val2, if necessary
			if (t == ME_KEYDOWN)
				for (int k = 0; k<count; k++)
					*output++ = evs[k].value2;
		}
	}
	printf("Done. MIDI compressed to %i bytes\n", (int)(output-startOutput));
	
	return (int)(output-startOutput);
}
